use super::{
    err, err_value,
    file::{list_mod, scan_mod},
    json, mod_dir, ok, throw_err, Base, ECode, JsonFile, LockFile, MapSv, RNull, Resp, SvCache,
    VBase, VMap, Value, VecV,
};
use tracing::instrument;

pub struct CfgCache;

impl Base for CfgCache {}

impl CfgCache {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, cache: SvCache) -> Resp {
        let data = para.ckobj()?;

        match cmd {
            "list" => self.do_list(data, cache).await,
            "plc" => self.do_plc(data, cache).await,
            "get" => self.do_get(data, cache).await,
            _ => throw_err!(ECode::NoServe, "no cache serve"),
        }
    }

    async fn do_plc(&self, data: &MapSv, cache: SvCache) -> Resp {
        let id = data.ck_str("uid")?;

        let mut ret = Vec::new();

        if let Some(all) = self.get_id(&cache, "it") {
            for d in all.iter() {
                let d = d.ckstr()?;
                let tmp = format!("{id}.");

                if d.starts_with(&tmp) {
                    if let Some(v) = cache.get(d) {
                        ret.push(v);
                    }
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "plc iter empty");
        }

        ok!(ret)
    }

    async fn do_list(&self, data: &MapSv, cache: SvCache) -> Resp {
        let md = data.ck_str("mod")?;

        if md != "it" && md != "plc" {
            throw_err!(ECode::Para, "must be it or plc");
        }

        let mut ret = Vec::new();

        if let Some(all) = self.get_id(&cache, md) {
            for d in all.iter() {
                let cache_id = d.ckstr()?;

                if let Some(v) = cache.get(cache_id) {
                    ret.push(v);
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "plc it empty");
        }

        ok!(ret)
    }

    async fn do_get(&self, data: &MapSv, cache: SvCache) -> Resp {
        let uid = data.ck_str("uid")?;

        let ret = cache
            .get(uid)
            .ok_or(err_value(ECode::EmptyData, "uid empty"))?;

        ok!(ret)
    }

    fn get_id(&self, cache: &SvCache, key: &str) -> Option<VecV> {
        let ret = cache.get(key)?;
        let ret = ret.array()?;

        Some(ret.clone())
    }
}

#[instrument(skip_all, name = "update_all")]
pub async fn update_all(lock: &LockFile, cache: SvCache) -> RNull {
    let r = lock.read().await;

    let _ = update_plc(&cache).await?;
    let _ = update_it(&cache).await?;

    drop(r);

    Ok(())
}

pub async fn update_plc(cache: &SvCache) -> RNull {
    if let Some(file) = list_mod("plc") {
        for cache_id in file.iter() {
            let r = mod_dir("plc", cache_id);
            let body = JsonFile::read_file(&r)?;

            cache.insert(cache_id.to_string(), body).await;
        }
    }

    let _ = scan_mod("plc", &cache).await;

    Ok(())
}

#[instrument(skip_all, name = "update_it")]
pub async fn update_it(cache: &SvCache) -> RNull {
    if let Some(file) = list_mod("it") {
        for cache_id in file.iter() {
            let r = mod_dir("it", cache_id);

            let body = JsonFile::read_file(&r)?;
            let body = body.ckobj()?;

            cache.insert(cache_id.to_string(), json!(body)).await;
        }
    }

    let _ = scan_mod("it", &cache).await;

    Ok(())
}
